using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using RSCore.Files;

namespace RSCore;

public sealed record class RSDirectory(string Name) : IHjsonSerializable
{
	public IEnumerable<RSDirectory> Directories => _directories;

	public IEnumerable<RSFile> Files => _files;

	public RSDirectory? Parent { get; set; }

	public void AddDirectory(RSDirectory directory)
	{
		directory.Parent = this;
		_directories.Add(directory);
	}

	public void AddFile(RSFile file)
	{
		file.Parent = this;
		_files.Add(file);
	}

	public void Deserialize(JsonValue json)
	{
		static void DeserializeInternal(RSDirectory directory, JsonObject obj)
		{
			Debug.Assert(obj != null);
			Debug.Assert(obj.GetText(nameof(Name)) == directory.Name);
			foreach (var fileJson in obj[nameof(Files)].OfType<JsonObject>())
			{
				var name = fileJson.GetText(nameof(Name));
				var extension = fileJson.GetText(nameof(Extensions));
				var file = RSFile.Create(name, extension);
				file.Deserialize(fileJson["Data"]);
				directory.AddFile(file);
			}

			foreach (var dirJson in obj[nameof(Directories)].OfType<JsonObject>())
			{
				var dir = new RSDirectory(obj.GetText(nameof(Name)));
				DeserializeInternal(dir, obj);
			}
		}

		var obj = json as JsonObject;
		Debug.Assert(obj != null);
		DeserializeInternal(this, obj);
	}

	public IEnumerable<RSFile> EnumerateFile()
	{
		foreach (var file in _files)
		{
			yield return file;
		}
		foreach (var sub in _directories)
		{
			foreach (var subFile in sub.EnumerateFile())
			{
				yield return subFile;
			}
		}
	}

	public RSDirectory GetDirectory(string name)
	{
		return _directories.First(dir => dir.Name == name);
	}

	public ref RSDirectory GetDirectoryRefOrNullRef(string name)
	{
		for (int i = 0; i < _directories.Count; i++)
		{
			if (_directories[i].Name == name)
			{
				return ref CollectionsMarshal.AsSpan(_directories)[i];
			}
		}
		return ref Unsafe.NullRef<RSDirectory>();
	}

	public RSFile GetFile(string name)
	{
		return _files.First(file => file.Name == name);
	}

	public ref RSFile GetFileRefOrNullRef(string name)
	{
		for (int i = 0; i < _files.Count; i++)
		{
			if (_files[i].Name == name)
			{
				return ref CollectionsMarshal.AsSpan(_files)[i];
			}
		}
		return ref Unsafe.NullRef<RSFile>();
	}

	public RSDirectory GetOrAddDirectory(string name)
	{
		foreach (var dir in _directories)
		{
			if (dir.Name == name)
			{
				return dir;
			}
		}
		var child = new RSDirectory(name);
		AddDirectory(child);
		return child;
	}

	public RSFile GetOrAddFile(string name, Func<RSFile> factory)
	{
		foreach (var file in _files)
		{
			if (file.Name == name)
			{
				return file;
			}
		}
		var child = factory.Invoke();
		AddFile(child);
		return child;
	}

	public void RemoveDirectory(RSDirectory directory) => _directories.Remove(directory);

	public void RemoveFile(RSFile file) => _files.Remove(file);

	public void CopyTo(RSDirectory target, bool overwrite = false)
	{
		foreach (var file in _files)
		{
			if (target.TryGetFile(file.Name, out var exist))
			{
				if (overwrite)
				{
					target.RemoveFile(exist);
					target.AddFile(file with { });
				}
			}
			else
			{
				target.AddFile(file with { });
			}
		}

		foreach (var dir in _directories)
		{
			if (!target.TryGetDirectory(dir.Name, out var exist))
			{
				exist = new RSDirectory(dir.Name);
				target.AddDirectory(exist);
			}
			dir.CopyTo(exist);
		}
	}

	public JsonValue Serialize()
	{
		static void SerializeInternal(RSDirectory directory, out JsonObject json)
		{
			var fileJson = new JsonArray();
			var dirJson = new JsonArray();
			json = new JsonObject()
			{
				{ nameof(Name), directory.Name }, { nameof(Files), fileJson }, { nameof(Directories), dirJson }
			};

			foreach (var file in directory.Files)
			{
				var obj = new JsonObject
				{
					{ nameof(Name), file.Name },
					{ nameof(Extensions), file.Extension },
					{ "Data", file.Serialize() },
				};
				fileJson.Add(obj);
			}

			foreach (var dir in directory.Directories)
			{
				SerializeInternal(dir, out var result);
				dirJson.Add(result);
			}
		}

		SerializeInternal(this, out var result);
		return result;
	}

	public bool TryGetDirectory(string name, [NotNullWhen(true)] out RSDirectory? result)
	{
		result = null;
		foreach (var dir in _directories)
		{
			if (dir.Name == name)
			{
				result = dir;
				return true;
			}
		}
		return false;
	}

	public bool TryGetFile(string name, [NotNullWhen(true)] out RSFile? result)
	{
		result = null;
		foreach (var file in _files)
		{
			if (file.Name == name)
			{
				result = file;
				return true;
			}
		}
		return false;
	}

	private List<RSDirectory> _directories = new();

	private List<RSFile> _files = new();
}